#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include "my_log.h"

namespace poponion
{
  namespace common
  {
    const char * LevelName[16][16] = 
    {
      "ALL",
      "DEBUG",
      "INFO",
      "WARN",
      "ERROR",
      "NONE"
    };
    MyLog MyLog::logger_; 

    MyLog::MyLog():max_file_len_(MAX_LOGFILE_SIZE), level_(INFO), file_num_(0)
    {
      memset(files_, 0, MAX_FILENAME_LEN*MAX_LOGFILE_NUM);
    }
    
    MyLog::~MyLog()
    {
      if (file_name_ != NULL)
      {
        free(file_name_);
        file_name_ = NULL;
        close(fd_);
      }
    }

    int MyLog::init(const char* file_name,LogLevel lev, int32_t file_size)
    {
      int ret = MY_SUCCESS;
      if (lev < ALL || lev > NONE || file_size > MAX_LOGFILE_SIZE
          || file_size < 0 || file_name == NULL || strlen(file_name) == 0)
      {
        ret = MY_ERROR;
      }
      else
      {
        level_ = lev;
        max_file_len_ = file_size;
        file_name_ = strdup(file_name);
        
        //O_APPEND thread safe
        fd_ = open(file_name_, O_RDWR | O_APPEND | O_TRUNC | O_CREAT, 0640);
        if (-1 == fd_)
        {
          ret = MY_ERROR;
        }
      }
      return ret;
    }
    
    int MyLog::setLogLevel(LogLevel lev)
    {
      int ret = MY_SUCCESS;
      if (ALL > lev || NONE < lev)
      {
        ret = MY_ERROR;
      }
      else
      {
        level_ = lev;
      }
      return ret;
    }

    int MyLog::setMaxFileSize(int32_t size)
    {
      int ret = MY_SUCCESS;
      if (MAX_LOGFILE_SIZE < size || 0 >= size)
      {
        ret = MY_ERROR;
      }
      else
      {
        max_file_len_ = size;
      }
    }
    
    void MyLog::writeLog(LogLevel lev, const char* file, int line, const char* func,
                         const char* fmt, ...)
    {
      if (lev >= level_)
      {
        char buffer[4096]; //max sigle line length is 4k
        va_list args;
        va_start(args, fmt);
        vsnprintf(buffer, 4096, fmt, args);
        va_end(args);
        int ret = rotateLog();
        if (MY_SUCCESS == ret)
        {
          char logbuf[5000];
          time_t t;
          time(&t);
          struct tm *sec = localtime(&t);
          char timebuf[64];
          strftime(timebuf, 64, "%c", sec);
          ssize_t size = snprintf(logbuf, 5000, "[%s] %s %s:%d %s %s\n", timebuf, 
                                  LevelName[lev], file, line, func, buffer);
          write(fd_, logbuf, size);
        }
      }
    }
    
    int MyLog::rotateLog()
    {
      int ret = MY_SUCCESS;
      off_t size = lseek(fd_, 0, SEEK_END);
      if (max_file_len_ <= size)
      {
        close(fd_);
        char backfile[MAX_FILENAME_LEN];
        //change log file
        if (file_num_ < 10)
        {
          ret = backupLogFile(backfile);
          if (MY_SUCCESS == ret)
          {
            memcpy(files_[file_num_], backfile, strlen(backfile));
            ++file_name_;
          }
        }
        else
        {
          //delete oldest log file
          unlink(files_[0]);
          ret = backupLogFile(backfile);
          for(int idx = 0; idx < MAX_LOGFILE_NUM - 1; ++idx)
          {
            memcpy(files_[idx+1], files_[idx], MAX_FILENAME_LEN);
          }
          memcpy(files_[MAX_LOGFILE_NUM - 1], backfile, MAX_FILENAME_LEN);
        }
      }
      return ret;
    }

    int MyLog::backupLogFile(char *bak_buf)
    {
      int ret = MY_SUCCESS;
      if (NULL == file_name_)
      {
        ret = MY_ERROR;
      }
      else
      {
        time_t t;
        time(&t);
        struct tm *timespc = ::localtime(&t);
        char timebuf[64];
        strftime(timebuf, 64, "%s", timespc);
        snprintf(bak_buf, MAX_FILENAME_LEN, "%s.%s", file_name_, timebuf);
        rename(file_name_, bak_buf);
        fd_ = open(file_name_, O_RDWR | O_APPEND | O_TRUNC | O_CREAT, 0640);
        if (-1 == fd_)
        {
          ret = MY_ERROR;
        }
      }
      return ret;
    }
  }//end namespace common
}//end namespace poponion
